package fag.core.game.test.playground;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import fag.core.game.AbstractDoodad;
import fag.core.game.ResourceManager;
import fag.core.game.Scene;
import fag.core.game.ResourceManager.Loading;
import fag.core.game.test.playground.doodad.LoadingBar;
import fag.core.game.test.playground.solid.AbstractSolid;
import fag.core.game.test.playground.solid.Circle;
import fag.core.game.test.playground.solid.Polygon;
import fag.core.game.test.playground.solid.Rectangle;
import fag.core.geometry.Polygon2D;
import fag.core.geometry.Rectangle2D;
import fag.core.geometry.Vector2D;

/**
 * Scène du bac à sable. Elle est responsable dans un premier temps de simuler une opération de chargement avant de s'initialiser
 * avec une plateforme centrale.
 * @author lumz
 */
public class PlaygroundScene extends Scene implements PropertyChangeListener {
	
	// Aspects
	private static final Color BACKGROUND_COLOR = Color.LIGHT_GRAY;
	
	// Chargement
	private LoadingBar loadingBar = new LoadingBar();
	private static final String PROGRESS_PROPERTY_NAME = "progress";
	
	// Engendrement
	private static final int SPAWN_DELAY = 50;
	private static final int SPAWN_OFFSET = 200;
	private static final int[] SPAWN_VELOCITY_RANGE = {800, 1400};
	private static final double[] SPAWN_VELOCITY_ANGLE_RANGE = {Math.PI / 3, Math.PI / 3 * 2};
	
	private static final Dimension[] SPAWN_SIZE_RANGE = {new Dimension(25, 25), new Dimension(75, 75)};

	// Membres
	private Dimension size = null;
	private Vector2D spawn = null;
	
	private boolean isInitialized = false;
	private double delay = 0;
	
	private List<AbstractSolid> solids = new ArrayList<AbstractSolid>();
	
	/**
	 * Constructeur par défaut.
	 * @param size La taille de la scène.
	 */
	public PlaygroundScene(Dimension size) {
		setSize(size);
		startLoading();
	}
	
	/**
	 * Constructeur de confort.
	 * @param width La largeur de la scène.
	 * @param height La hauteur de la scène.
	 */
	public PlaygroundScene(int width, int height) {
		setSize(new Dimension(width, height));
		startLoading();
	}
	
	/**
	 * Accesseur de la hauteur de la scène.
	 * @return La hauteur de la scène.
	 */
	public int getHeight() {
		return (int)size.getHeight();
	}
	
	/**
	 * Accesseur de la liste des solides.
	 * @return La liste des solides.
	 */
	public List<AbstractSolid> getSolids() {
		return new ArrayList<AbstractSolid>(solids);
	}
	
	/**
	 * Accesseur de la largeur de la scène.
	 * @return La largeur de la scène.
	 */
	public int getWidth() {
		return (int)size.getWidth();
	}
	
	/**
	 * Accesseur de la valeur de vérité de l'initialisation.
	 * @return La valeur de vérité de l'initialisation.
	 */
	public boolean isInitialized() {
		return isInitialized;
	}
	
	@Override
	public void propertyChange(PropertyChangeEvent event) {
		
		if(event.getPropertyName().equals(PROGRESS_PROPERTY_NAME)) {
			
			int progress = (Integer)event.getNewValue();
			
			if(progress == 100)
				initialize();
			else
				loadingBar.setProgress(progress);
		}
	}
	
	/**
	 * Mutateur de la taille de la scène.
	 * @param size La nouvelle taille de la scène.
	 */
	public void setSize(Dimension size) {
		
		this.size = size;
		
		getQuadtree().setBounds(new Rectangle2D(-size.getWidth() / 2, -size.getHeight() / 2, size.getWidth(), size.getHeight()));
		spawn = new Vector2D(size.getWidth() / 2, size.getHeight() / 2 + SPAWN_OFFSET);
	}
	
	@Override
	public void step(double dt) {
		
		if(isInitialized) {
			
			delay += dt;
			if(delay >= SPAWN_DELAY) {
				
				delay -= SPAWN_DELAY;
				spawn();
			}
			
			deleteOutbounded();
			
			for(AbstractSolid solid : solids)
				solid.step(dt / 1000);
		}
	}
	
	@Override
	protected void drawBackground(Graphics2D graphics2d) {
		
		graphics2d.setColor(BACKGROUND_COLOR);
		graphics2d.fillRect(0, 0, (int)size.getWidth(), (int)size.getHeight());
	}

	@Override
	protected void drawForeground(Graphics2D graphics2d) {

	}
		
	/**
	 * Suppression des doodads hors scène.
	 */
	private void deleteOutbounded() {
		
		List<AbstractDoodad> doodads = this.getDoodads();
		
		Iterator<AbstractDoodad> it = doodads.iterator();
		while(it.hasNext()) {
			
			AbstractDoodad doodad = it.next();
			
			Vector2D location = doodad.getTranslation();
			
			if(location.x < 0 || location.x > size.getWidth() || location.y < 0 || location.y > size.getHeight()) {
				removeDoodad(doodad);
				solids.remove(doodad);
			}
		}
	}
	
	/**
	 * Initialiser la scène.
	 */
	private void initialize() {
		
		removeDoodad(loadingBar);
		loadingBar = null;

		isInitialized = true;
	}
	
	/**
	 * Engendrer un nouveau solide dans la scène.
	 */
	private void spawn() {
		
		AbstractSolid solid = null;
		
		Dimension range = new Dimension((int)(SPAWN_SIZE_RANGE[1].getWidth() - SPAWN_SIZE_RANGE[0].getWidth()), (int)(SPAWN_SIZE_RANGE[1].getHeight() - SPAWN_SIZE_RANGE[0].getHeight()));
		
		// Nouveau solide de forme rectangulaire
		int random = (int)(Math.random() * 3);
		switch(random) {
		case 0:
			solid = new Rectangle((int)(Math.random() * range.getWidth() + SPAWN_SIZE_RANGE[0].getWidth()), (int)(Math.random() * range.getHeight() + SPAWN_SIZE_RANGE[0].getHeight()));
			break;
			
		case 1:			
			solid = new Circle(Math.min((int)(Math.random() * range.getWidth() + SPAWN_SIZE_RANGE[0].getWidth()), (int)(Math.random() * range.getHeight() + SPAWN_SIZE_RANGE[0].getHeight())) / 2);
			break;
			
		case 2:
			{
				List<Vector2D> vertices = new ArrayList<Vector2D>();
				
				for(int i = 0; i < 20; ++i)
					vertices.add(new Vector2D(Math.random() * range.getWidth() - range.getWidth() / 2, Math.random() * range.getHeight() - range.getHeight() / 2));
				
				solid = new Polygon(Polygon2D.getConvexHull(vertices).getVertices());
			}

		}

		
		double fireAngle = Math.random() * (SPAWN_VELOCITY_ANGLE_RANGE[1] - SPAWN_VELOCITY_ANGLE_RANGE[0]) + SPAWN_VELOCITY_ANGLE_RANGE[0];	
		solid.setLinearVelocity(new Vector2D(Math.cos(fireAngle), -Math.sin(fireAngle)).mul((SPAWN_VELOCITY_RANGE[1] - SPAWN_VELOCITY_RANGE[0]) * Math.random() + SPAWN_VELOCITY_RANGE[0]));
		
		solids.add(solid);
		addDoodad(solid);
		solid.setTranslation(spawn);
	}
	
	/**
	 * Commencer le chargement de la scène (simulation).
	 */
	private void startLoading() {
		
		addDoodad(loadingBar);
		loadingBar.setTranslation(size.getWidth() / 2, size.getHeight() / 2);
		
		ResourceManager.Loading loading = ResourceManager.get().new Loading() {

			@Override
			public Map<String, Object> load() {
			
				// Simulation de chargement
				for(int i = 1; i <= 100; ++i) {
					
					try {
						sleep(10);
					}
					catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					// Avertir le thread 
					firePropertyChange(PROGRESS_PROPERTY_NAME, i - 1, i);
				}
				
				return new HashMap<String, Object>();
			}
		};
		
		loading.addPropertyChangeListener(this);
		loading.start();
	}
}
